#!/usr/bin/python
import os
import sys
import optparse
import operator
import glob
import stat
from StringIO import StringIO

# Third-party modules
from configmod.configobj import configobj
from configmod.configobj import validate

# Uya modules
from configmod import misc
from configmod import config as configmod
import general
import interfaces
import routes
import validators
import paths

debug = misc.debug
default_config_file = "/etc/uya/uya.conf"
default_template_file = "/usr/share/uya/uya.template.conf"
hash_file = "/var/spool/uya/hashmod"

def run_config(config, run_commands=True):
    """Run all configuration process given a config and template file."""
    return_info = []
    info = return_info.append
        
    # General
    info(general.hostname(config["general"]["hostname"]))
    
    # NTP    
    info(general.ntpdate(config["general"]["ntp_servers"], unprevileged=True))
           
    # Network interfaces    
    info(interfaces.network_interfaces(config["interfaces"], config["routes"]))
               
    # DNS
    dns = config["general"]
    info(general.dns([dns["dns_primary"], dns["dns_secondary"]]))

    # Routes
    info(routes.network_routes(config["general"]["hostname"], config))
    
    # Debug modified files
    modified_filenames, commands = zip(*return_info)
    modified_filenames = list(misc.uniq(reduce(operator.add, modified_filenames)))
    commands = list(misc.uniq(map(tuple, reduce(operator.add, commands))))
    for modified_filename in modified_filenames:
        debug(2, "modified file: %s" % modified_filename)
    for command in misc.uniq(commands):
        debug(2, "run: %s" % repr(command))
        if run_commands:
            misc.run(list(command))
        else: debug(2, "testing mode, command not run")         
        
    return modified_filenames, commands        

def get_config(config, template_file):
    """Run all configuration process given a config and template file."""
       
    # General
    general.hostname_get(config["general"])
    general.dns_get(config["general"])
    general.ntpdate_get(config["general"])
    
    # Interfaces
    interfaces.network_interfaces_get(config["interfaces"])

    # Routes
    routes.network_routes_get(config["routes"])
    
    # Return new config only if validates
    validator = validate.Validator(validators.get_validators())    
    fake = StringIO()
    config.write(fake)
    try:
        configmod.load_config(StringIO(fake.getvalue()), 
            template_file, validator)
    except configmod.ConfigError, errors:
        debug(1, config)
        debug(0, "Error on original files, correct them to complete " +
            "read_config function,")
    else:
        return config

def get_modification_hash():
    """Return a string that can be used as modification hash."""
    filenames = misc.flatten(glob.glob(filename.replace("%s", "*")) 
        for filename in paths.default_filenames.values())
    ifaces = interfaces.get_system_interfaces()
    hashv = "\n".join([
        "\n".join(["%s: %s" % (fn, str(os.stat(fn)[stat.ST_MTIME])) 
            for fn in filenames]),
        "interfaces: %s" % (" ".join(ifaces))]) + "\n"
    return hashv

def changed_hash():
    """Return True if the modification hash has changed."""
    new_hash = get_modification_hash()
    if os.path.isfile(hash_file):
        old_hash = open(hash_file).read()
    else: old_hash = None
    return (old_hash != new_hash)

def update_hash():
    open(hash_file, "w").write(get_modification_hash())
            
def main(args, return_info=False):        
    """Process arguments and run configurations."""
    usage = """usage: uya [options]

    Easy router configuration.""" 
    parser = optparse.OptionParser(usage)
    parser.add_option('-v', '--verbose', dest='verbose_level', action="count",
        default=0, help='Increase verbose level (0=Error, 1=Info, 2=Debug)')
    parser.add_option('-f', '--config-file', dest='config_file',
        default=default_config_file, metavar="FILE", type="string", help='config file')
    parser.add_option('-t', '--template-file', dest='template_file',
        default=default_template_file, metavar="FILE", type="string", help='template file')
    parser.add_option('-s', '--skip-commands', dest='skip_commands',
        default=False, action="store_true", help="Don't run commands")
    parser.add_option('-r', '--read-config', dest='read_config',
        default=False, action="store_true", help="Read configuration files")
    parser.add_option('-w', '--write-config', dest='write_config',
        default=False, action="store_true", help="Write configuration files")
    parser.add_option('', '--list-files', dest='list_files',
        action="store_true", default=False, help='List configuration files')
    options, args0 = parser.parse_args(args)
    
    misc.verbose_level = options.verbose_level
    if not options.read_config and not options.write_config:
        debug(0, "Error: You must either write or read configuration.\n")
        parser.print_help()
        return 1
    debug(1, "config: (%s - %s)" % (options.config_file, options.template_file))
    validator = validate.Validator(validators.get_validators())
    config = configmod.load_config(options.config_file, 
        options.template_file, validator, ignore_errors=options.read_config)
    if not config:
        return 1    
    run_commands = not options.skip_commands
    if options.read_config:
        config = get_config(config, options.template_file)
        if config:
            config.write()            
            update_hash()
        else: return 2
    elif options.write_config:
        modified_filenames, commands = run_config(config, run_commands)
        if return_info:
            return 0, modified_filenames, commands        
    
if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
